package com.example.util;

import com.example.supplierfinancin.Receipt;
import org.fisco.bcos.asset.contract.SupplierFinancin;
import org.fisco.bcos.sdk.BcosSDK;
import org.fisco.bcos.sdk.abi.datatypes.generated.tuples.generated.*;
import org.fisco.bcos.sdk.client.Client;
import org.fisco.bcos.sdk.crypto.keypair.CryptoKeyPair;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class ContractTools {
    private static BcosSDK bcosSDK;
    public static void initialize(){
        @SuppressWarnings("resource")
        ApplicationContext context =
                new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        bcosSDK = context.getBean(BcosSDK.class);
    }
    public static Tuple2<String, String> generate_key_pair(){

        Client client = bcosSDK.getClient(1);
        CryptoKeyPair cryptoKeyPair = client.getCryptoSuite().createKeyPair();
        client.getCryptoSuite().setCryptoKeyPair(cryptoKeyPair);
        Tuple2<String,String> temp=new Tuple2<>(cryptoKeyPair.getAddress(),cryptoKeyPair.getHexPrivateKey());
        return temp;
    }

    private static String loadAssetAddr() throws Exception {
        // load Asset contact address from contract.properties
        Properties prop = new Properties();
        final Resource contractResource = new ClassPathResource("contract.properties");
        prop.load(contractResource.getInputStream());

        String contractAddress = prop.getProperty("address");
        if (contractAddress == null || contractAddress.trim().equals("")) {
            throw new Exception(" load Asset contract address failed, please deploy it first. ");
        }
        return contractAddress;
    }
    private static Tuple5<String,String, BigInteger, BigInteger, BigInteger> select_company(Client c, CryptoKeyPair ckp){
        try{
            String contractAddress = loadAssetAddr();
            SupplierFinancin asset = SupplierFinancin.load(contractAddress,c,ckp);
            Tuple6<Boolean, String, String, BigInteger, BigInteger, BigInteger> temp=asset.getSelectCompanyOutput(asset.selectCompany(ckp.getAddress()));
            if(temp.getValue1()==false){
                throw new NullPointerException("failed : "+temp.getValue2());
            }
            return new Tuple5<String,String, BigInteger, BigInteger, BigInteger>(ckp.getAddress(),temp.getValue3(),temp.getValue4(),temp.getValue5(),temp.getValue6());
        }catch (NullPointerException e){
            System.out.println(e.getMessage());
        }
        catch (Exception e){
            System.out.printf(" load asset account failed, error message is %s\n", e.getMessage());
        }
        return null;
    }
    public static Tuple5<String,String, BigInteger, BigInteger, BigInteger> get_company_details(String privatekey){
        Tuple2<Client,CryptoKeyPair> account=get_client(privatekey);
        return select_company(account.getValue1(),account.getValue2());
    }

    public static Tuple2<Client,CryptoKeyPair> get_client(String privatekey){
        Client c=bcosSDK.getClient(1);;
        CryptoKeyPair t=c.getCryptoSuite().createKeyPair(privatekey);
        c.getCryptoSuite().setCryptoKeyPair(t);
        Tuple2<Client,CryptoKeyPair> ret=new Tuple2<>(c,t);
        return ret;
    }
    private static Boolean contract_register_bank(Client c,CryptoKeyPair ckp,String assetAccount,String uscc) {
        try {
            String contractAddress = loadAssetAddr();
            SupplierFinancin asset = SupplierFinancin.load(contractAddress, c, ckp);
            Tuple2<Boolean, String> ret=asset.getRegisterBankOutput(asset.registerBank(assetAccount,uscc));
            if(ret.getValue1()==false) {
                throw new NullPointerException("failed : " + ret.getValue2());
            }
            return true;
        }catch (NullPointerException e){
            System.out.println(e.getMessage());
        }
        catch (Exception e) {
            System.out.printf(" load asset account failed, error message is %s\n", e.getMessage());
        }
        return false;
    }
    private static Boolean contract_register_company(Client c,CryptoKeyPair ckp,String assetAccount,String uscc) {
        try {
            String contractAddress = loadAssetAddr();
            SupplierFinancin asset = SupplierFinancin.load(contractAddress, c, ckp);
            Tuple2<Boolean, String> ret=asset.getRegisterBankOutput(asset.registerCompany(assetAccount,uscc));
            if(ret.getValue1()==false) {
                throw new NullPointerException("failed : " + ret.getValue2());
            }
            return true;
        }catch (NullPointerException e){
            System.out.println(e.getMessage());
        }
        catch (Exception e) {
            System.out.printf(" load asset account failed, error message is %s\n", e.getMessage());
        }
        return false;
    }
    public static Boolean register_bank(String privatekey,String company,String uscc){
        Tuple2<Client,CryptoKeyPair> account=get_client(privatekey);
        return contract_register_bank(account.getValue1(), account.getValue2(), company,uscc);
    }
    public static Boolean register_company(String privatekey,String company,String uscc){
        Tuple2<Client,CryptoKeyPair> account=get_client(privatekey);
        return contract_register_company(account.getValue1(), account.getValue2(), company,uscc);
    }

    private static Boolean contract_transfer_receipt(Client c,CryptoKeyPair ckp,String debtee, BigInteger amount, BigInteger deadline){
        try{
            String contractAddress = loadAssetAddr();
            SupplierFinancin asset = SupplierFinancin.load(contractAddress,c,ckp);
            Tuple2<Boolean, String> ret=asset.getTransferReceiptOutput(asset.transferReceipt(debtee,amount,deadline));
            if(ret.getValue1()==false){
                throw new NullPointerException("failed : "+ret.getValue2());
            }
            return true;
        }catch (NullPointerException e){
            System.out.println(e.getMessage());
        }
        catch (Exception e){
            System.out.printf(" load asset account failed, error message is %s\n", e.getMessage());
        }
        return false;
    }
    public static Boolean transfer_receipt(String privatekey,String debtee,BigInteger amount, BigInteger deadline){
        Tuple2<Client,CryptoKeyPair> account=get_client(privatekey);
        return contract_transfer_receipt(account.getValue1(), account.getValue2(), debtee,amount,deadline);
    }
    private static Boolean contract_return_receipt(Client c,CryptoKeyPair ckp,BigInteger receiptId, BigInteger amount){
        try{
            String contractAddress = loadAssetAddr();
            SupplierFinancin asset = SupplierFinancin.load(contractAddress,c,ckp);
            Tuple2<Boolean, String> ret=asset.getReturnReceiptOutput(asset.returnReceipt(receiptId,amount));
            if(ret.getValue1()==false){
                throw new NullPointerException("failed : "+ret.getValue2());
            }
            return true;
        }catch (NullPointerException e){
            System.out.println(e.getMessage());
        }
        catch (Exception e){
            System.out.printf(" load asset account failed, error message is %s\n", e.getMessage());
        }
        return false;
    }
    public static Boolean return_receipt(String privatekey,BigInteger receiptId, BigInteger amount){
        Tuple2<Client,CryptoKeyPair> account=get_client(privatekey);
        return contract_return_receipt(account.getValue1(), account.getValue2(), receiptId,amount);
    }
    private static List<BigInteger> get_receipt_all_id(Client c,CryptoKeyPair ckp,String tableName, String key){
        try{
            String contractAddress = loadAssetAddr();
            SupplierFinancin asset = SupplierFinancin.load(contractAddress,c,ckp);
            Tuple1<List<BigInteger>> ret=asset.getGetReceiptAllIdOutput(asset.getReceiptAllId(tableName,key));
            return ret.getValue1();
        }catch (Exception e){
            System.out.printf(" load asset account failed, error message is %s\n", e.getMessage());
        }
        return null;
    }
    private static Tuple5<String, String, BigInteger, BigInteger, BigInteger> select_receipt(Client c,CryptoKeyPair ckp,String tableName, String key, BigInteger receiptId){
        try{
            String contractAddress = loadAssetAddr();
            SupplierFinancin asset = SupplierFinancin.load(contractAddress,c,ckp);
            Tuple7<Boolean, String, String, String, BigInteger, BigInteger, BigInteger> ret=asset.getSelectReceiptOutput(asset.selectReceipt(tableName,key,receiptId));

            if(ret.getValue1()==false){
                throw new NullPointerException("failed : "+ret.getValue2());
            }
            return new Tuple5<String, String, BigInteger, BigInteger, BigInteger>(ret.getValue3(),ret.getValue4(),ret.getValue5(),ret.getValue6(),ret.getValue7());
        }catch (NullPointerException e){
            System.out.println(e.getMessage());
        }
        catch (Exception e){
            System.out.printf(" load asset account failed, error message is %s\n", e.getMessage());
        }
        return null;
    }
    private static List<Receipt> contract_get_all_receipt(Client c,CryptoKeyPair ckp,String tableName, String key){
        List<BigInteger> ids=get_receipt_all_id(c,ckp,tableName,key);
        List<Receipt> temp=new ArrayList<Receipt>();
        for(BigInteger i : ids){
            Tuple5<String, String, BigInteger, BigInteger, BigInteger> t=select_receipt(c,ckp,tableName,key,i);
            if(t==null){
                return null;
            }
            Receipt receipt=new Receipt(t.getValue1(),t.getValue2(),t.getValue3(),t.getValue4(),t.getValue5());
            temp.add(receipt);
        }
        return temp;
    }
    public static List<Receipt> get_all_in_receipt(String privatekey){
        Tuple2<Client,CryptoKeyPair> account=get_client(privatekey);
        return contract_get_all_receipt(account.getValue1(),account.getValue2(),"t_in_receipt",account.getValue2().getAddress());
    }
    public static List<Receipt> get_all_out_receipt(String privatekey){
        Tuple2<Client,CryptoKeyPair> account=get_client(privatekey);
        return contract_get_all_receipt(account.getValue1(),account.getValue2(),"t_out_receipt",account.getValue2().getAddress());
    }
    private static void recordAssetAddr(String address) throws FileNotFoundException, IOException {
        Properties prop = new Properties();
        prop.setProperty("address", address);
        final Resource contractResource = new ClassPathResource("contract.properties");
        FileOutputStream fileOutputStream = new FileOutputStream(contractResource.getFile());
        prop.store(fileOutputStream, "contract address");
    }
    private static Boolean deployAssetAndRecordAddr(Client c,CryptoKeyPair ckp) {
        try {
            SupplierFinancin asset = SupplierFinancin.deploy(c, ckp);
            System.out.println(
                    " deploy Asset success, contract address is " + asset.getContractAddress());
            recordAssetAddr(asset.getContractAddress());
            return true;
        } catch (Exception e) {
            System.out.println(" deploy Asset contract failed, error message is  " + e.getMessage());
            return false;
        }
    }
    public static Boolean deploycontract(String privatekey){
        Tuple2<Client,CryptoKeyPair> account=get_client(privatekey);
        return deployAssetAndRecordAddr(account.getValue1(), account.getValue2());
    }
}
